package com.alibaba.cloud.utils;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * The Class DefaultUUIdUtil.
 *
 * @ClassName: DefaultUUIdUtil
 * @Description:雪花算法改造 
 * @author: zhb1316783262@163.com
 * @date: 2020年4月1日 上午10:59:33
 * @code +------+---------------+-----------------+----------------+-------------------+---------------+
 *       | sign | delta seconds | version         | worker node id | clock callback    |sequence       |
 *       +------+---------------+-----------------+----------------+-------------------+---------------+
 *     64 1bits    63-35           3bits  34-32     31-17 15bits      16-15 2bits        14-1 14bits }
 */
public class DefaultUUIdUtil {

    /** The Constant log. */
    private static final Logger log = LoggerFactory.getLogger(DefaultUUIdUtil.class);

    /**  分配位数. */
    private static final int SIGN_BITS_SIZE = 1;
    
    /** 时间位. */
    private static final int TIME_BITS_SIZE = 29;
    
    /** 版本控制位. */
    private static final int VERSION_BITS_SIZE = 3;
    
    /** 数据中心分配. */
    private static final int WORKER_BITS_SIZE = 15;
    
    /** 时钟回拨. */
    private static final int CLOCK_CALLBACK_BITS_SIZE = 2;
    
    /** 序列位. */
    private static final int SEQ_BITS_SIZE = 14;

    /** 最大值. */
    // private final long maxVersion = ~(-1L << versionBitsSize);
    private final long MAX_DELTASECONDS = ~(-1L << TIME_BITS_SIZE);
    
    /** The max worker id. */
    private final long MAX_WORKER_ID = ~(-1L << WORKER_BITS_SIZE);
    
    /** The max clock callback. */
    private final long MAX_CLOCK_CALLBACK = ~(-1L << CLOCK_CALLBACK_BITS_SIZE);
    
    /** The max sequence. */
    private final long MAX_SEQUENCE = ~(-1L << SEQ_BITS_SIZE);

    /** 偏移量. */
    private final int TIMESTAMP_SHIFT = VERSION_BITS_SIZE + WORKER_BITS_SIZE + CLOCK_CALLBACK_BITS_SIZE + SEQ_BITS_SIZE;
    
    /** The version shift. */
    private final int VERSION_SHIFT = WORKER_BITS_SIZE + CLOCK_CALLBACK_BITS_SIZE + SEQ_BITS_SIZE;
    
    /** The worker id shift. */
    private final int WORKER_ID_SHIFT = CLOCK_CALLBACK_BITS_SIZE + SEQ_BITS_SIZE;
    
    /** The clock callback shift. */
    private final int CLOCK_CALLBACK_SHIFT = SEQ_BITS_SIZE;

    /** The Constant VERSION. */
    public static final long VERSION = 1L;
    
    /** The worker id. */
    private final long WORKER_ID;
    
    /**  指定起点时间. */
    private final long EPOCH_SECONDS = 1582992000L; // 默认使用2020-03-01 00:00:01 时间

    /**  运行时记录数据 *. */
    private long lastSecond = -1L;
    
    /** The clock callback. */
    private long clockCallback = 0L;
    
    /** The sequence. */
    private long sequence = 0L;

    /**
     * Instantiates a new default UU id util.
     *
     * @param workerId the worker id
     */
    public DefaultUUIdUtil(long workerId) {
        int allocateTotalBits = SIGN_BITS_SIZE + VERSION_BITS_SIZE + TIME_BITS_SIZE + WORKER_BITS_SIZE
                + CLOCK_CALLBACK_BITS_SIZE + SEQ_BITS_SIZE;
        if (allocateTotalBits != Long.SIZE) {
            log.error("ehomepay_infra_uuid_error allocate not enough 64 bits。 allocateTotalBits= {} ",
                    allocateTotalBits);
            throw new RuntimeException("allocate not enough 64 bits。 allocateTotalBits = " + allocateTotalBits);
        }
        this.WORKER_ID = workerId & MAX_WORKER_ID;
    }

    /**
     * Next id.
     *
     * @return the long
     */
    public synchronized long nextId() {
        long currentSecond = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
        // 时钟回拨
        if (currentSecond < lastSecond) {
            long refusedSeconds = lastSecond - currentSecond;
            clockCallback = (clockCallback + 1) & MAX_CLOCK_CALLBACK;
            log.warn("ehomepay_infra_uuid_error 时钟回拨。workerId:%d, 当前时间:%d, 回拨: %d 秒", WORKER_ID, currentSecond,
                    refusedSeconds);
        }
        sequence = (sequence + 1) & MAX_SEQUENCE;
        lastSecond = currentSecond;
        long deltaSeconds = (currentSecond - EPOCH_SECONDS) & MAX_DELTASECONDS;
        // 生成long id
        return (deltaSeconds << TIMESTAMP_SHIFT) | (VERSION << VERSION_SHIFT) | (WORKER_ID << WORKER_ID_SHIFT)
                | (clockCallback << CLOCK_CALLBACK_SHIFT) | sequence;
    }

    /**
     * 解析uid组成内容二进制.
     *
     * @param uid the uid
     * @return the string
     */
    public String parseUuid2Binary(long uid) {
        long sequence = (uid << (Long.SIZE - SEQ_BITS_SIZE)) >>> (Long.SIZE - SEQ_BITS_SIZE);
        long clockCallback = (uid << (Long.SIZE - SEQ_BITS_SIZE - CLOCK_CALLBACK_BITS_SIZE)) >>> (Long.SIZE
                - CLOCK_CALLBACK_BITS_SIZE);
        long workerId = (uid << (Long.SIZE - SEQ_BITS_SIZE - CLOCK_CALLBACK_BITS_SIZE
                - WORKER_BITS_SIZE)) >>> (Long.SIZE - WORKER_BITS_SIZE);
        long verison = (uid << (SIGN_BITS_SIZE + TIME_BITS_SIZE)) >>> (Long.SIZE - VERSION_BITS_SIZE);
        long deltaSeconds = uid >>> (Long.SIZE - SIGN_BITS_SIZE - TIME_BITS_SIZE);
        StringBuilder builder = new StringBuilder();
        builder.append("uid=" + uid).append(",uidBinary=").append(Long.toBinaryString(uid)).append(",uidBinaryFormat=")
                .append(toBin(uid, Long.SIZE)).append(",timestamp=" + toBin(deltaSeconds, TIME_BITS_SIZE))
                .append(",verison=" + toBin(verison, VERSION_BITS_SIZE))
                .append(",workerId=" + toBin(workerId, WORKER_BITS_SIZE))
                .append(",clockCallback=" + toBin(clockCallback, CLOCK_CALLBACK_BITS_SIZE))
                .append(",sequence=" + toBin(sequence, SEQ_BITS_SIZE));
        return builder.toString();
    }

    /**
     * 解析uid组成内容.
     *
     * @param uid the uid
     * @return the string
     */
    public String parseUID2String(long uid) {
        // parse UID
        long sequence = (uid << (Long.SIZE - SEQ_BITS_SIZE)) >>> (Long.SIZE - SEQ_BITS_SIZE);
        long clockCallback = (uid << (Long.SIZE - SEQ_BITS_SIZE - CLOCK_CALLBACK_BITS_SIZE)) >>> (Long.SIZE
                - CLOCK_CALLBACK_BITS_SIZE);
        long workerId = (uid << (Long.SIZE - SEQ_BITS_SIZE - CLOCK_CALLBACK_BITS_SIZE
                - WORKER_BITS_SIZE)) >>> (Long.SIZE - WORKER_BITS_SIZE);
        long verison = (uid << (SIGN_BITS_SIZE + TIME_BITS_SIZE)) >>> (Long.SIZE - VERSION_BITS_SIZE);
        long deltaSeconds = uid >>> (Long.SIZE - SIGN_BITS_SIZE - TIME_BITS_SIZE);
        // 使用 2020-03-01 00:00:01 加秒差值
        Date thatTime = new Date(TimeUnit.SECONDS.toMillis(EPOCH_SECONDS + deltaSeconds));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String thatTimeStr = sdf.format(thatTime);
        return String.format(
                "{\"UID\":\"%d\",\"timestamp\":\"%s\",\"verison\":\"%s\",\"workerId\":\"%d\",\"clockCallback\":\"%d\",\"sequence\":\"%d\"}",
                uid, thatTimeStr, verison, workerId, clockCallback, sequence);
    }

    /**
     * To bin.
     *
     * @param num the num
     * @param size the size
     * @return the string
     */
    private static String toBin(long num, int size) {
        char[] chs = new char[size];
        for (int i = 0; i < size; i++) {
            chs[size - 1 - i] = (char) ((num >> i & 1) + '0');
        }
        return new String(chs);
    }
}
